﻿using System;
using System.Collections.Generic;
using System.Text;

using SensorShell;
using TeamFoundationServerSensor;

namespace VisualStudioSensor {

    /// <summary>
    /// Used to communicate with the sensorshell from the event handlers.
    /// </summary>
    internal class SensorShellProxy {

        internal static readonly SensorShellProxy TheInstance = new SensorShellProxy();

        #region Fields
        /// <summary>
        /// The name of the tool that this sensorshell is sensing.
        /// </summary>
        private string tool = "Unknown";

        /// <summary>
        /// The java hame directory.
        /// </summary>
        private string javaHome;

        /// <summary>
        /// The sensorshell.jar location
        /// </summary>
        private string sensorShellPath;

        /// <summary>
        /// An instance of the sensorshell.
        /// </summary>
        private SensorShell.SensorShell shell;
        #endregion

        /// <summary>
        /// Constructor for creating object.
        /// </summary>
        private SensorShellProxy() {
        }

        #region Controls
        /// <summary>
        /// For compatibility.
        /// </summary>
        /// <param name="javaHome">The JavaHome path.</param>
        /// <param name="sensorShellPath">The SensorShell path.</param>
        /// <returns>True if shell started, false otherwise.</returns>
        internal Boolean Start(string javaHome, string sensorShellPath) {
            return this.Start("VisualStudio", javaHome, sensorShellPath);
        }

        /// <summary>
        /// Starts the SensorShell and the sensorshell.jar subprocess for a 
        /// specified tool.
        /// </summary>
        /// <param name="tool">The name of the tool the sensor is using.</param>
        /// <param name="javaHome">The JavaHome path.</param>
        /// <param name="sensorShellPath">The SensorShell path.</param>
        /// <returns>True if shell started, false otherwise.</returns>
        internal Boolean Start(string tool, string javaHome, string sensorShellPath) {

            Boolean success = false;

            lock (this) {
                this.javaHome = javaHome;
                this.sensorShellPath = sensorShellPath;
                if (this.shell == null) {
                    this.shell = new SensorShell.SensorShell(tool, javaHome, sensorShellPath);
                    this.shell.CommandInput += new SensorShellMessageDelegate(this.SensorShellProcess_Stdin);
                    this.shell.StdoutOutput += new SensorShellMessageDelegate(this.SensorShellProcess_Stdout);
                    this.shell.StderrOutput += new SensorShellMessageDelegate(this.SensorShellProcess_Stderr);
                    try {
                        this.tool = tool;
                        this.shell.Start();
                        success = true;
                    }
                    catch (SensorShellException ex) {
                        Logger.TheInstance.Log(ex);
                        success = false;
                    }
                }
            }

            return success;
        }

        /// <summary>
        /// Stop the SensorShell and the sensorshell.jar subprocess.
        /// </summary>
        /// <param name="timeoutMillis">The number of milliseconds to wait before quitting.</param>
        internal void Stop(int timeoutMillis) {
            lock (this) {
                if (this.shell != null) {
                    this.shell.CommandInput -= new SensorShellMessageDelegate(this.SensorShellProcess_Stdin);
                    this.shell.StdoutOutput -= new SensorShellMessageDelegate(this.SensorShellProcess_Stdout);
                    this.shell.StderrOutput -= new SensorShellMessageDelegate(this.SensorShellProcess_Stderr);
                    Logger.TheInstance.Log("Sending final batch of sensor data to Hackystat server.",
                      LogLevel.Info);
                    Logger.TheInstance.Log("This may take up to 15 seconds.", LogLevel.Info);
                    this.shell.Quit(timeoutMillis);
                    this.shell = null;
                }
            }
        }

        /// <summary>
        /// Returns whether the process is running or not.
        /// </summary>
        public Boolean status() {
            lock (this) {
                return shell != null;
            }
        }
        #endregion

        #region Send Data
        /// <summary>
        /// Utility method to send open and close file data.
        /// </summary>
        /// <param name="type">The type of event (Edit)</param>
        /// <param name="subtype">The subtype of the edit (OpenFile or CloseFile)</param>
        /// <param name="fileAndPath">The full path of the file.</param>
        /// <param name="file">The name of the file.</param>
        internal void SendOpenCloseFileData(string type, string subtype, string fileAndPath, string file) {
            try {
                this.shell.DoCommand("add#Tool=" + tool
                    + "#SensorDataType=DevEvent#Type=" + type
                    + "#Subtype=" + subtype
                    + "#Resource=" + fileAndPath
                    + "#Unit-Type=File" + "#Unit-Name=" + file);
            }
            catch (SensorShellException ex) {
                Logger.TheInstance.Log(ex);
            }
        }

        /// <summary>
        /// Utility method to send build result events to the sensorbase.
        /// </summary>
        /// <param name="result">The result of the build. True or false.</param>
        /// <param name="type">The type of build that occurred.</param>
        /// <param name="target">The action of build that occurred.</param>
        /// <param name="resource">A URI resource for the build.</param>
        /// <param name="pMap">
        /// The map that contains all other key-value pairs needed for the event.
        /// </param>
        internal void SendBuildEventData(Boolean result, string type,
            string target, string resource, IDictionary<string, string> pMap) {

            string resultTxt = "Failure";
            if (result) resultTxt = "Success";

            string cmd = "add#Tool=" + tool
                + "#Resource=" + resource
                + "#SensorDataType=Build"
                + "#Result=" + resultTxt
                + "#Target=" + target
                + "#Type=" + type;

            SendData(cmd, pMap);
        }

        /// <summary>
        /// Utility method to send source control events to the sensorbase.
        /// </summary>
        /// <param name="type">The type of commit item. (add, delete, etc.)</param>
        /// <param name="commitTime">The UTC commit time.</param>
        /// <param name="file">The full path and name of the file.</param>
        /// <param name="author">The user who made the commit.</param>
        /// <param name="revision">The revision number of the commit.</param>
        /// <param name="linesAdded">Number of lines added to file.</param>
        /// <param name="linesDeleted">Number of lines deleted from file.</param>
        /// <param name="pMap">The map that contains all other key-value pairs needed for the event.
        /// </param>
        internal void SendCommitEventData(string type, string commitTime,
            string file, string author, string revision, int linesAdded,
            int linesDeleted, IDictionary<string, string> pMap) {
            string cmd = "add#Tool=" + tool
                + "#SensorDataType=Commit"
                + "#CommitTime=" + commitTime
                + "#Type=" + type
                + "#Resource=" + file
                + "#Author=" + author
                + "#Version=" + revision
                + "#linesAdded=" + linesAdded + "#linesDeleted=" + linesDeleted;

            SendData(cmd, pMap);
        }

        /// <summary>
        /// Utility method to send developer events to the sensorbase.
        /// </summary>
        /// <param name="type">The type of event (Edit)</param>
        /// <param name="resource">The full path and name of the resource.</param>
        /// <param name="pMap">The map that contains all other key-value pairs needed for the event.
        /// </param>
        internal void SendDevEventData(String type, String resource, IDictionary<string, string> pMap) {
            string cmd = "add#Tool=" + tool
                + "#SensorDataType=DevEvent"
                + "#Type=" + type
                + "#Resource=" + resource;

            SendData(cmd, pMap);
        }

        /// <summary>
        /// Utility method to send review activity data.
        /// </summary>
        /// <param name="tool">The tool that made the review.</param>
        /// <param name="resource">Unique resource of this review.</param>
        /// <param name="reviewID">
        /// The unique ID associated with this review.</param>
        /// <param name="phase">The phase of review when this activity took 
        /// place, such as "individual", "team", or "rework".</param>
        /// <param name="file">The file that is being reviewd.</param>
        /// <param name="pMap">The map that contains all other key-value pairs 
        /// needed for the event.
        /// </param>
        internal void SendReviewActivityData(string tool, string resource,
            string reviewID, string phase, string file,
            IDictionary<string, string> pMap) {

            string cmd = "add#Tool=" + tool
                + "#SensorDataType=ReviewActivity"
                + "#Resource=" + resource
                + "#ReviewID=" + reviewID
                + "#Phase=" + phase
                + "#File=" + file;

            SendData(cmd, pMap);
        }

        /// <summary>
        /// Utility method to send UnitTest data.
        /// </summary>
        /// <param name="path">The path of the resource</param>
        /// <param name="result">The unit test result.</param>
        /// <param name="pMap">The map that contains all other key-value pairs needed for the event.
        /// </param>
        internal void SendUnitTestData(string path, string result, IDictionary<string, string> pMap) {
            string cmd = "add#Tool=" + tool
                + "#SensorDataType=UnitTest"
                + "#Resource=" + path
                + "#Result=" + result;

            SendData(cmd, pMap);
        }

        /// <summary>
        /// Utility method to send custom event types.
        /// </summary>
        /// <param name="name">The name of this custom data type.</param>
        /// <param name="resource">The unique resource of this event.</param>
        /// <param name="pMap">The map that contains all other key-value pairs 
        /// needed for the data type.
        /// </param>
        internal void SendCustomData(string name, string resource, IDictionary<string, string> pMap) {
            string cmd = "add#Tool=" + tool
                + "#SensorDataType=" + name
                + "#Resource=" + resource;

            SendData(cmd, pMap);
        }

        /// <summary>
        /// General method for sending event data.
        /// </summary>
        /// <param name="cmd">The event command.</param>
        /// <param name="pMap">Extra data to send with event.</param>
        private void SendData(string cmd, IDictionary<string, string> pMap) {

            // Add extra data to the cmd.
            if (pMap != null) {
                foreach (KeyValuePair<string, string> kvp in pMap) {
                    cmd += "#" + kvp.Key + "=" + kvp.Value;
                }
            }

            // Try to send data to Hackystat server.
            try {
                this.shell.DoCommand(cmd);
            }
            catch (SensorShellException ex) {
                Logger.TheInstance.Log(ex);
            }
        }
        #endregion

        #region Stream Redirection
        /// <summary>
        /// Called when sensorshell sub-process receives input from stdin.
        /// </summary>
        /// <param name="message">SensorShell input.</param>
        private void SensorShellProcess_Stdin(string message) {
            Logger.TheInstance.Log("[SensorShell Stdin] " + message, LogLevel.Info);
        }

        /// <summary>
        /// Called when sensorshell sub-process sends output to stdout.
        /// </summary>
        /// <param name="message">SensorShell output.</param>
        private void SensorShellProcess_Stdout(string message) {
            Logger.TheInstance.Log("[SensorShell Stdout] " + message, LogLevel.Info);
        }

        /// <summary>
        /// Called when sensorshell sub-process sends output to stderr.
        /// </summary>
        /// <param name="message">Sensorshell error.</param>
        private void SensorShellProcess_Stderr(string message) {
            Logger.TheInstance.Log("[SensorShell Stderr] " + message, LogLevel.Info);
        }
        #endregion

        #region Helpers

        /// <summary>
        /// Returns the JavaHome path.
        /// </summary>
        internal string JavaHome {
            get {
                lock (this) { return this.javaHome; }
            }
        }

        /// <summary>
        /// Returns the SensorShell path.
        /// </summary>
        internal string SensorShellPath {
            get {
                lock (this) { return this.sensorShellPath; }
            }
        }

        #endregion
    }
}
